{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZloPIuRHn97X"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors. [Licensed under the Apache License, Version 2.0](#scrollTo=Afd8bu4xJOgh)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tNgCmfUvJNoF"
      },
      "outputs": [],
      "source": [
        "// #@title Licensed under the Apache License, Version 2.0 (the \"License\"); { display-mode: \"form\" }\n",
        "// Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "// you may not use this file except in compliance with the License.\n",
        "// You may obtain a copy of the License at\n",
        "//\n",
        "// https://www.apache.org/licenses/LICENSE-2.0\n",
        "//\n",
        "// Unless required by applicable law or agreed to in writing, software\n",
        "// distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "// See the License for the specific language governing permissions and\n",
        "// limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AlvdCHw5JGyx"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/swift/tutorials/protocol_oriented_generics\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">View on TensorFlow.org</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/swift/blob/master/docs/site/tutorials/protocol_oriented_generics.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Run in Google Colab</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/swift/blob/master/docs/site/tutorials/protocol_oriented_generics.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">GitHub에서 소스 보기</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c_1u7JSBMx3x"
      },
      "source": [
        "# 프로토콜 지향 프로그래밍 및 제네릭\n",
        "\n",
        "이 튜토리얼에서는 프로토콜 지향 프로그래밍과 일상적인 예제에서 제네릭과 함께 사용되는 방법에 대한 다양한 예제를 살펴 봅니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LP0gMw56TlvH"
      },
      "source": [
        "## 프로토콜\n",
        "\n",
        "상속은 프로그램의 여러 구성 요소 간에 코드를 공유할 수 있도록 프로그래밍 언어로 코드를 구성하는 강력한 방법입니다.\n",
        "\n",
        "Swift에는 상속을 표현하는 여러 가지 방법이 있습니다. 다른 언어를 접해봤다면 이러한 방법 중 하나인 클래스 상속에 이미 익숙할 것입니다. 그러나 Swift에는 프로토콜이라는 방법이 있습니다.\n",
        "\n",
        "이 튜토리얼에서는 다른 요소의 균형을 맞춰 유사한 목표를 달성할 수 있는 하위 클래스화의 대안인 프로토콜을 살펴봅니다. Swift에서 프로토콜은 여러 추상 멤버를 포함합니다. 클래스, 구조체 및 열거형은 여러 프로토콜을 준수할 수 있으며, 준수 관계는 소급하여 설정할 수 있습니다. 이 모든 것은 하위 클래스화를 사용하여 Swift에서 쉽게 표현할 수 없는 일부 설계를 가능하게 합니다. 프로토콜(확장 및 프로토콜 제약)의 사용과 프로토콜의 한계를 지원하는 관용구를 살펴보겠습니다.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5AIIH5Q59b41"
      },
      "source": [
        "## Swift 💖의 값 형식!\n",
        "\n",
        "참조 의미 체계가 있는 클래스 외에도 Swift는 값으로 전달되는 열거형 및 구조체를 지원합니다. 열거형과 구조체는 클래스에서 제공하는 많은 기능을 지원합니다. 한 번 살펴보겠습니다!\n",
        "\n",
        "먼저 열거형이 클래스와 어떻게 유사한지 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zCN0Uc0w-gng"
      },
      "outputs": [],
      "source": [
        "enum Color: String {\n",
        "    case red = \"red\"\n",
        "    case green = \"green\"\n",
        "    case blue = \"blue\"\n",
        "    // A computed property. Note that enums cannot contain stored properties.\n",
        "    var hint: String {\n",
        "        switch self {\n",
        "            case .red:\n",
        "                return \"Roses are this color.\"\n",
        "            case .green:\n",
        "                return \"Grass is this color.\"\n",
        "            case .blue:\n",
        "                return \"The ocean is this color.\"\n",
        "        }\n",
        "    }\n",
        "    \n",
        "    // An initializer like for classes.\n",
        "    init?(color: String) {\n",
        "        switch color {\n",
        "        case \"red\":\n",
        "            self = .red\n",
        "        case \"green\":\n",
        "            self = .green\n",
        "        case \"blue\":\n",
        "            self = .blue\n",
        "        default:\n",
        "            return nil\n",
        "        }\n",
        "    }\n",
        "}\n",
        "\n",
        "// Can extend the enum as well!\n",
        "extension Color {\n",
        "    // A function.\n",
        "    func hintFunc() -> String {\n",
        "        return self.hint\n",
        "    }\n",
        "}\n",
        "\n",
        "let c = Color.red\n",
        "print(\"Give me a hint for c: \\(c.hintFunc())\")\n",
        "\n",
        "let invalidColor = Color(color: \"orange\")\n",
        "print(\"is invalidColor nil: \\(invalidColor == nil)\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ueiGy9gCgypk"
      },
      "source": [
        "이제 구조체를 살펴보겠습니다. 구조체는 상속할 수 없지만 대신 프로토콜을 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ns4qCE1pg3uJ"
      },
      "outputs": [],
      "source": [
        "struct FastCar {\n",
        "    // Can have variables and constants as stored properties.\n",
        "    var color: Color\n",
        "    let horsePower: Int\n",
        "    // Can have computed properties.\n",
        "    var watts: Float {\n",
        "       return Float(horsePower) * 745.7\n",
        "    }\n",
        "    // Can have lazy variables like in classes!\n",
        "    lazy var titleCaseColorString: String = {\n",
        "        let colorString = color.rawValue\n",
        "        return colorString.prefix(1).uppercased() + \n",
        "               colorString.lowercased().dropFirst()\n",
        "    }()\n",
        "    // A function.\n",
        "    func description() -> String {\n",
        "        return \"This is a \\(color) car with \\(horsePower) horse power!\"\n",
        "    }\n",
        "    // Can create a variety of initializers.\n",
        "    init(color: Color, horsePower: Int) {\n",
        "        self.color = color\n",
        "        self.horsePower = horsePower\n",
        "    }\n",
        "    // Can define extra initializers other than the default one.\n",
        "    init?(color: String, horsePower: Int) {\n",
        "        guard let enumColor = Color(color: color) else {\n",
        "            return nil\n",
        "        }\n",
        "        self.color = enumColor\n",
        "        self.horsePower = horsePower\n",
        "    }\n",
        "}\n",
        "\n",
        "var car = FastCar(color: .red, horsePower: 250)\n",
        "print(car.description())\n",
        "print(\"Horse power in watts: \\(car.watts)\")\n",
        "print(car.titleCaseColorString)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1Hw8bpQIlaWT"
      },
      "source": [
        "마지막으로 클래스와 달리 값 형식이 어떻게 전달되는지 살펴보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "C67qzGBVlhdo"
      },
      "outputs": [],
      "source": [
        "// Notice we have no problem modifying a constant class with \n",
        "// variable properties.\n",
        "class A {\n",
        "  var a = \"a\"\n",
        "}\n",
        "\n",
        "func foo(_ a: A) {\n",
        "  a.a = \"foo\"\n",
        "}\n",
        "let a = A()\n",
        "print(a.a)\n",
        "foo(a)\n",
        "print(a.a)\n",
        "\n",
        "/* \n",
        "Uncomment the following code to see how an error is thrown.\n",
        "Structs are implicitly passed by value, so we cannot modify it.\n",
        "> \"error: cannot assign to property: 'car' is a 'let' constant\"\n",
        "*/\n",
        "\n",
        "// func modify(car: FastCar, toColor color: Color) -> Void {\n",
        "//   car.color = color\n",
        "// }\n",
        "\n",
        "// car = FastCar(color: .red, horsePower: 250)\n",
        "// print(car.description())\n",
        "// modify(car: &car, toColor: .blue)\n",
        "// print(car.description())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0BNxC5RyoKM7"
      },
      "source": [
        "## 프로토콜 사용하기\n",
        "\n",
        "여러 자동차에 대한 프로토콜을 만드는 것으로 시작하겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3ZSm1uTWoJ0h"
      },
      "outputs": [],
      "source": [
        "protocol Car {\n",
        "    var color: Color { get set }\n",
        "    var price: Int { get }\n",
        "    func turnOn()\n",
        "    mutating func drive()\n",
        "}\n",
        "\n",
        "protocol Electric {\n",
        "    mutating func recharge()\n",
        "    // percentage of the battery level, 0-100%.\n",
        "    var batteryLevel: Int { get set }\n",
        "}\n",
        "\n",
        "protocol Gas {\n",
        "    mutating func refill()\n",
        "    // # of liters the car is holding, varies b/w models.\n",
        "    var gasLevelLiters: Int { get set }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aV_F6MyLps3h"
      },
      "source": [
        "객체 지향 세계(다중 상속 없음)에서는 `Electric` 및 `Gas` 추상 클래스를 만든 다음 클래스 상속을 사용하여 `Car`에서 둘 다 상속한 다음 특정 자동차 모델을 기본 클래스로 만들었을 것입니다. 그러나 여기서는 둘 다 **zero** 결합을 가진 완전히 별개의 프로토콜입니다! 이를 통해 더욱 융통성 있게 전체 시스템을 설계할 수 있습니다.\n",
        "\n",
        "Tesla를 정의해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LkGRtwz3psrP"
      },
      "outputs": [],
      "source": [
        "struct TeslaModelS: Car, Electric {\n",
        "    var color: Color // Needs to be a var since `Car` has a getter and setter.\n",
        "    let price: Int\n",
        "    var batteryLevel: Int\n",
        "    \n",
        "    func turnOn() {\n",
        "        print(\"Starting all systems!\")\n",
        "    }\n",
        "\n",
        "    mutating func drive() {\n",
        "        print(\"Self driving engaged!\")\n",
        "        batteryLevel -= 8\n",
        "    }\n",
        "\n",
        "    mutating func recharge() {\n",
        "        print(\"Recharging the battery...\")\n",
        "        batteryLevel = 100\n",
        "    }\n",
        "}\n",
        "\n",
        "var tesla = TeslaModelS(color: .red, price: 110000, batteryLevel: 100)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "K5KB0IGLrkcm"
      },
      "source": [
        "이것은 `Car` 및 `Electric` 프로토콜을 모두 준수하는 새로운 구조체 `TeslaModelS`를 지정합니다.\n",
        "\n",
        "이제 가스 구동 자동차를 정의해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GPLKFUAOrMp-"
      },
      "outputs": [],
      "source": [
        "struct Mustang: Car, Gas{\n",
        "    var color: Color\n",
        "    let price: Int\n",
        "    var gasLevelLiters: Int\n",
        "    \n",
        "    func turnOn() {\n",
        "        print(\"Starting all systems!\")\n",
        "    }\n",
        "    \n",
        "    mutating func drive() {\n",
        "        print(\"Time to drive!\")\n",
        "        gasLevelLiters -= 1\n",
        "    }\n",
        "    \n",
        "    mutating func refill() {\n",
        "        print(\"Filling the tank...\")\n",
        "        gasLevelLiters = 25\n",
        "    }\n",
        "}\n",
        "\n",
        "var mustang = Mustang(color: .red, price: 30000, gasLevelLiters: 25)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jrbCRkglsi_d"
      },
      "source": [
        "### 기본 동작으로 프로토콜 확장하기\n",
        "\n",
        "예제에서 알 수 있는 것은 중복성이 있다는 것입니다. 전기 자동차를 충전할 때마다 배터리 비율 수준을 100으로 설정해야 합니다. 모든 전기 자동차의 최대 용량은 100%이지만, 가스 자동차는 가스 탱크 용량에 따라 다르기 때문에 전기 자동차의 경우 기본 수준을 100으로 설정할 수 있습니다.\n",
        "\n",
        "이것이 Swift의 확장이 유용할 수 있는 부분입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NiHUJxXMtzSg"
      },
      "outputs": [],
      "source": [
        "extension Electric {\n",
        "    mutating func recharge() {\n",
        "        print(\"Recharging the battery...\")\n",
        "        batteryLevel = 100\n",
        "    }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v8QbTb9NxWqI"
      },
      "source": [
        "이제부터 생성되는 새로운 전기 자동차는 모두 충전할 때 배터리를 100으로 설정하게 됩니다. 고유한 기본 동작으로 클래스, 구조체 및 열거형을 데코레이팅할 수 있었던 것입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VIXakqxtvkp_"
      },
      "source": [
        "![프로토콜 코믹](https://koenig-media.raywenderlich.com/uploads/2015/06/protocols-extend.png)\n",
        "\n",
        "만화를 제공한 [Ray Wenderlich](https://www.raywenderlich.com/814-introducing-protocol-oriented-programming-in-swift-3)에게 감사드립니다!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MAo8n3zUR8Q9"
      },
      "source": [
        "그러나 한 가지 주의해야 할 사항은 다음과 같습니다. 첫 번째 구현에서 `foo()`를 `A`의 기본 구현으로 정의하지만, 프로토콜에서는 필수로 지정하지 않습니다. 따라서 `a.foo()`를 불러내면 '`A default`'가 출력됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "koP20_C9R7ps"
      },
      "outputs": [],
      "source": [
        "protocol Default {}\n",
        "\n",
        "extension Default {\n",
        "    func foo() { print(\"A default\")}\n",
        "}\n",
        "\n",
        "struct DefaultStruct: Default {\n",
        "    func foo() {\n",
        "        print(\"Inst\")\n",
        "    }\n",
        "}\n",
        "\n",
        "let a: Default = DefaultStruct()\n",
        "a.foo()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uSE5IWF_Sdet"
      },
      "source": [
        "그러나 `A`에 `foo()`를 요구하면 '`Inst`'를 얻습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DJ8jstIWSoUP"
      },
      "outputs": [],
      "source": [
        "protocol Default {\n",
        "    func foo()\n",
        "}\n",
        "\n",
        "extension Default {\n",
        "    func foo() { \n",
        "        print(\"A default\")\n",
        "    }\n",
        "}\n",
        "\n",
        "struct DefaultStruct: Default {\n",
        "    func foo() {\n",
        "        print(\"Inst\")\n",
        "    }\n",
        "}\n",
        "\n",
        "let a: Default = DefaultStruct()\n",
        "a.foo()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XC1juXPzZV8Q"
      },
      "source": [
        "이는 첫 번째 예제의 정적 디스패치와 Swift의 프로토콜에 대한 두 번째 예제의 정적 디스패치 간의 차이로 인해 발생합니다. 자세한 내용은 이 [매체 게시물](https://medium.com/@PavloShadov/https-medium-com-pavloshadov-swift-protocols-magic-of-dynamic-static-methods-dispatches-dfe0e0c85509)을 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HzB0XC0wwMLD"
      },
      "source": [
        "### 기본 동작 재정의하기\n",
        "\n",
        "그러나 원하는 경우 기본 동작을 재정의할 수 있습니다. 한 가지 중요한 점은 이것이 [동적 디스패치를 지원하지 않는다는 것](https://stackoverflow.com/questions/44703205/swift-protocol-extension-method-is-called-instead-of-method-implemented-in-subcl)입니다.\n",
        "\n",
        "예전 버전의 전기 자동차가 있어 배터리 상태가 90%로 감소했다고 가정해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CaIZhcsVyBKz"
      },
      "outputs": [],
      "source": [
        "struct OldElectric: Car, Electric {\n",
        "    var color: Color // Needs to be a var since `Car` has a getter and setter.\n",
        "    let price: Int\n",
        "    var batteryLevel: Int\n",
        "    \n",
        "    func turnOn() {\n",
        "        print(\"Starting all systems!\")\n",
        "    }\n",
        "    \n",
        "    mutating func drive() {\n",
        "        print(\"Self driving engaged!\")\n",
        "        batteryLevel -= 8\n",
        "    }\n",
        "    \n",
        "    mutating func reCharge() {\n",
        "        print(\"Recharging the battery...\")\n",
        "        batteryLevel = 90\n",
        "    }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c_Xmw5cDy_rZ"
      },
      "source": [
        "## 프로토콜의 표준 라이브러리 사용\n",
        "\n",
        "이제 Swift에서 프로토콜이 동작하는 방식에 대해 알아봤으니 표준 라이브러리 프로토콜을 사용하는 몇 가지 일반적인 예를 살펴보겠습니다.\n",
        "\n",
        "### 표준 라이브러리 확장하기\n",
        "\n",
        "Swift에 이미 존재하는 형식에 추가 기능을 더하는 방법을 살펴보겠습니다. Swift의 형식은 내장되어 있지 않지만 구조체로서 표준 라이브러리의 일부이므로 쉽게 수행할 수 있습니다.\n",
        "\n",
        "배열이 정렬되어 있는지 확인하면서 요소 배열에 대해 이진 검색을 시도해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-Jfn3P3P1RDt"
      },
      "outputs": [],
      "source": [
        "extension Collection where Element: Comparable {\n",
        "    // Verify that a `Collection` is sorted.\n",
        "    func isSorted(_ order: (Element, Element) -> Bool) -> Bool {\n",
        "        var i = index(startIndex, offsetBy: 1)\n",
        "        \n",
        "        while i < endIndex {\n",
        "            // The longer way of calling a binary function like `<(_:_:)`, \n",
        "            // `<=(_:_:)`, `==(_:_:)`, etc.\n",
        "            guard order(self[index(i, offsetBy: -1)], self[i]) else {\n",
        "                return false\n",
        "            }\n",
        "            i = index(after: i)\n",
        "        }\n",
        "        return true\n",
        "    }\n",
        "    \n",
        "    // Perform binary search on a `Collection`, verifying it is sorted.\n",
        "    func binarySearch(_ element: Element) -> Index? {\n",
        "        guard self.isSorted(<=) else {\n",
        "            return nil\n",
        "        }\n",
        "        \n",
        "        var low = startIndex\n",
        "        var high = endIndex\n",
        "        \n",
        "        while low <= high {\n",
        "            let mid = index(low, offsetBy: distance(from: low, to: high)/2)\n",
        "\n",
        "            if self[mid] == element {\n",
        "                return mid\n",
        "            } else if self[mid] < element {\n",
        "                low = index(after: mid)\n",
        "            } else {\n",
        "                high = index(mid, offsetBy: -1)\n",
        "            }\n",
        "        }\n",
        "        \n",
        "        return nil\n",
        "    }\n",
        "}\n",
        "\n",
        "print([2, 2, 5, 7, 11, 13, 17].binarySearch(5)!)\n",
        "print([\"a\", \"b\", \"c\", \"d\"].binarySearch(\"b\")!)\n",
        "print([1.1, 2.2, 3.3, 4.4, 5.5].binarySearch(3.3)!)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CIh7qyFVqlaH"
      },
      "source": [
        "이를 위해 *'요소를 비파괴적으로 여러 번 통과할 수 있고 인덱싱된 아래 첨자로 액세스할 수 있는 시퀀스'*를 정의하는 [`Collection`](https://developer.apple.com/documentation/swift/collection) 프로토콜을 확장합니다. 배열은 대괄호 표기법을 사용하여 인덱싱할 수 있으므로 이 프로토콜을 확장하고자 합니다.\n",
        "\n",
        "마찬가지로 이 유틸리티 함수는 요소를 비교할 수 있는 배열에만 추가하려고 합니다. 이것이 `where Element: Comparable`이 있는 이유입니다.\n",
        "\n",
        "`where` 절은 Swift의 형식 시스템의 일부입니다. 이 내용은 다음에 자세히 다루겠지만, 간단히 말해서 작성하는 확장에 추가 요구 사항을 더할 수 있습니다. 예를 들어, 프로토콜 구현 형식을 요구하거나, 두 개 형식이 동일하도록 요구하거나, 클래스가 특정 슈퍼클래스를 갖도록 요구할 수 있습니다.\n",
        "\n",
        "[`Element`](https://developer.apple.com/documentation/swift/sequence/2908099-element)는 `Collection` 준수 형식에 있는 요소의 관련 형식입니다. `Element`는 [`Sequence`](https://developer.apple.com/documentation/swift/sequence) 프로토콜 내에서 정의되지만 `Collection`은 `Sequence`에서 상속되므로 `Element` 관련 형식을 상속합니다.\n",
        "\n",
        "[`Comparable`](https://developer.apple.com/documentation/swift/comparable)은 *'관계 연산자 `<`, `<=`, `>=` 및 `>`를 사용하여 비교할 수 있는 형식'*을 정의하는 프로토콜입니다. 정렬된 `Collection`에 대해 이진 검색을 수행하고 있으므로 이것은 물론 사실이어야 합니다. 그렇지 않으면 이진 검색에서 왼쪽 또는 오른쪽으로 재귀/반복할지 여부를 알 수 없습니다.\n",
        "\n",
        "구현에 대한 추가 정보로 사용된 `index(_:offsetBy:)` 함수에 대한 자세한 내용은 다음 [문서](https://developer.apple.com/documentation/swift/string/1786175-index)를 참조하세요."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TmqFx2layKs7"
      },
      "source": [
        "## 제네릭 + 프로토콜 = 💥\n",
        "\n",
        "제네릭과 프로토콜은 중복 코드를 방지하기 위해 올바르게 사용된다면 강력한 도구가 될 수 있습니다.\n",
        "\n",
        "먼저 Colab 책의 끝에 있는 제네릭에 대해 간략히 설명하는 다른 튜토리얼 [A Swift Tour](https://colab.research.google.com/github/tensorflow/swift/blob/master/docs/site/tutorials/a_swift_tour.ipynb)를 살펴보세요.\n",
        "\n",
        "제네릭에 대한 일반적인 이해가 있다고 가정하고 몇 가지 고급 용도를 빠르게 살펴보겠습니다.\n",
        "\n",
        "단일 형식에 여러 프로토콜을 준수하는 형식과 같은 여러 요구 사항이 있는 경우 다양한 옵션을 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HIjkHLGtz268"
      },
      "outputs": [],
      "source": [
        "typealias ComparableReal = Comparable & FloatingPoint\n",
        "\n",
        "func foo1<T: ComparableReal>(a: T, b: T) -> Bool {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo2<T: Comparable & FloatingPoint>(a: T, b: T) -> Bool {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo3<T>(a: T, b: T) -> Bool where T: ComparableReal {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo4<T>(a: T, b: T) -> Bool where T: Comparable & FloatingPoint {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo5<T: FloatingPoint>(a: T, b: T) -> Bool where T: Comparable {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "print(foo1(a: 1, b: 2))\n",
        "print(foo2(a: 1, b: 2))\n",
        "print(foo3(a: 1, b: 2))\n",
        "print(foo4(a: 1, b: 2))\n",
        "print(foo5(a: 1, b: 2))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DMCioS9Dz5Fh"
      },
      "source": [
        "상단에 있는 `typealias`의 사용에 주목하세요. 이렇게 하면 기존 형식의 명명된 별칭이 프로그램에 추가됩니다. 형식 별칭이 선언된 후 프로그램의 모든 위치에서 기존 형식 대신 별칭이 지정된 이름을 사용할 수 있습니다. 형식 별칭은 새 형식을 생성하지 않습니다. 단순히 이름이 기존 형식을 참조하도록 허용합니다.\n",
        "\n",
        "이제 프로토콜과 제네릭을 함께 사용할 수 있는 방법을 살펴보겠습니다.\n",
        "\n",
        "컴퓨터 매장에서 판매하는 모든 노트북에 대해 다음과 같은 요구 사항을 기준으로 매장 뒤에서 노트북을 정리하는 방법을 결정해야 한다고 가정해 보겠습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "oKvieIsw2YJW"
      },
      "outputs": [],
      "source": [
        "enum Box {\n",
        "    case small\n",
        "    case medium\n",
        "    case large\n",
        "}\n",
        "\n",
        "enum Mass {\n",
        "    case light\n",
        "    case medium\n",
        "    case heavy\n",
        "}\n",
        "\n",
        "// Note: `CustomStringConvertible` protocol lets us pretty-print a `Laptop`.\n",
        "struct Laptop: CustomStringConvertible {\n",
        "    var name: String\n",
        "    var box: Box\n",
        "    var mass: Mass\n",
        "    \n",
        "    var description: String {\n",
        "        return \"(\\(self.name) \\(self.box) \\(self.mass))\"\n",
        "    }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ey519vu04FgG"
      },
      "source": [
        "그러나 선반에는 무게 제한이 있으므로 `Laptop`을 질량별로 그룹화해야 하는 새로운 요구 사항이 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tVRuqx_q4jQ9"
      },
      "outputs": [],
      "source": [
        "func filtering(_ laptops: [Laptop], by mass: Mass) -> [Laptop] {\n",
        "    return laptops.filter { $0.mass == mass }\n",
        "}\n",
        "\n",
        "let laptops: [Laptop] = [\n",
        "    Laptop(name: \"a\", box: .small, mass: .light),\n",
        "    Laptop(name: \"b\", box: .large, mass: .medium),\n",
        "    Laptop(name: \"c\", box: .medium, mass: .heavy),\n",
        "    Laptop(name: \"d\", box: .large, mass: .light)\n",
        "]\n",
        "\n",
        "let filteredLaptops = filtering(laptops, by: .light)\n",
        "print(filteredLaptops)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lw_W5zW17UMc"
      },
      "source": [
        "그런데 `Mass`가 아닌 다른 것으로 필터링하려면 어떻게 해야 할까요?\n",
        "\n",
        "한 가지 옵션은 다음을 수행하는 것입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2qDVD9Yl8POQ"
      },
      "outputs": [],
      "source": [
        "// Define a protocol which will act as our comparator.\n",
        "protocol DeviceFilterPredicate {\n",
        "    associatedtype Device\n",
        "    func shouldKeep(_ item: Device) -> Bool\n",
        "}\n",
        "\n",
        "// Define the structs we will use for passing into our filtering function.\n",
        "struct BoxFilter: DeviceFilterPredicate {\n",
        "    typealias Device = Laptop\n",
        "    var box: Box \n",
        "    \n",
        "    func shouldKeep(_ item: Laptop) -> Bool {\n",
        "        return item.box == box\n",
        "    }\n",
        "}\n",
        "\n",
        "struct MassFilter: DeviceFilterPredicate {\n",
        "    typealias Device = Laptop  \n",
        "    var mass: Mass\n",
        "    \n",
        "    func shouldKeep(_ item: Laptop) -> Bool {\n",
        "        return item.mass == mass\n",
        "    }\n",
        "}\n",
        "\n",
        "// Make sure our filter conforms to `DeviceFilterPredicate` and that we are \n",
        "// filtering `Laptop`s.\n",
        "func filtering<F: DeviceFilterPredicate>(\n",
        "    _ laptops: [Laptop], \n",
        "    by filter: F\n",
        ") -> [Laptop] where Laptop == F.Device {\n",
        "    return laptops.filter { filter.shouldKeep($0) }\n",
        "}\n",
        "\n",
        "// Let's test the function out!\n",
        "print(filtering(laptops, by: BoxFilter(box: .large)))\n",
        "print(filtering(laptops, by: MassFilter(mass: .heavy)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yFx_6y0-CRHc"
      },
      "source": [
        "좋습니다! 이제 모든 노트북 제약 조건을 기준으로 필터링할 수 있습니다. 그러나 지금은 `Laptop`만 필터링할 수 있습니다.\n",
        "\n",
        "상자에 담겨 있고 질량이 있는 모든 것을 필터링할 수 있다면 어떨까요? 이 노트북이 보관된 창고는 노트북과는 고객 기반이 다른 서버를 보관하는 데도 사용될 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EzhE_K-eCvYt"
      },
      "outputs": [],
      "source": [
        "// Define 2 new protocols so we can filter anything in a box and which has mass.\n",
        "protocol Weighable {\n",
        "    var mass: Mass { get }\n",
        "}\n",
        "\n",
        "protocol Boxed {\n",
        "    var box: Box { get }\n",
        "}\n",
        "\n",
        "// Define the new Laptop and Server struct which have mass and a box.\n",
        "struct Laptop: CustomStringConvertible, Boxed, Weighable {\n",
        "    var name: String\n",
        "    var box: Box\n",
        "    var mass: Mass\n",
        "    \n",
        "    var description: String {\n",
        "        return \"(\\(self.name) \\(self.box) \\(self.mass))\"\n",
        "    }\n",
        "}\n",
        "\n",
        "struct Server: CustomStringConvertible, Boxed, Weighable {\n",
        "    var isWorking: Bool\n",
        "    var name: String\n",
        "    let box: Box\n",
        "    let mass: Mass\n",
        "\n",
        "    var description: String {\n",
        "        if isWorking {\n",
        "            return \"(working \\(self.name) \\(self.box) \\(self.mass))\"\n",
        "        } else {\n",
        "            return \"(notWorking \\(self.name) \\(self.box) \\(self.mass))\"\n",
        "        }\n",
        "    }\n",
        "}\n",
        "\n",
        "// Define the structs we will use for passing into our filtering function.\n",
        "struct BoxFilter<T: Boxed>: DeviceFilterPredicate {\n",
        "    var box: Box \n",
        "  \n",
        "    func shouldKeep(_ item: T) -> Bool {\n",
        "        return item.box == box\n",
        "    }\n",
        "}\n",
        "\n",
        "struct MassFilter<T: Weighable>: DeviceFilterPredicate {\n",
        "    var mass: Mass\n",
        "    \n",
        "    func shouldKeep(_ item: T) -> Bool {\n",
        "        return item.mass == mass\n",
        "    }\n",
        "}\n",
        "\n",
        "// Define the new filter function.\n",
        "func filtering<F: DeviceFilterPredicate, T>(\n",
        "    _ elements: [T], \n",
        "    by filter: F\n",
        ") -> [T] where T == F.Device {\n",
        "    return elements.filter { filter.shouldKeep($0) }\n",
        "}\n",
        "\n",
        "\n",
        "// Let's test the function out!\n",
        "let servers = [\n",
        "    Server(isWorking: true, name: \"serverA\", box: .small, mass: .heavy),\n",
        "    Server(isWorking: false, name: \"serverB\", box: .medium, mass: .medium),\n",
        "    Server(isWorking: true, name: \"serverC\", box: .large, mass: .light),\n",
        "    Server(isWorking: false, name: \"serverD\", box: .medium, mass: .light),\n",
        "    Server(isWorking: true, name: \"serverE\", box: .small, mass: .heavy)\n",
        "]\n",
        "\n",
        "let products = [\n",
        "    Laptop(name: \"a\", box: .small, mass: .light),\n",
        "    Laptop(name: \"b\", box: .large, mass: .medium),\n",
        "    Laptop(name: \"c\", box: .medium, mass: .heavy),\n",
        "    Laptop(name: \"d\", box: .large, mass: .light)\n",
        "]\n",
        "\n",
        "print(filtering(servers, by: BoxFilter(box: .small)))\n",
        "print(filtering(servers, by: MassFilter(mass: .medium)))\n",
        "\n",
        "print(filtering(products, by: BoxFilter(box: .small)))\n",
        "print(filtering(products, by: MassFilter(mass: .medium)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-UrRDSaFNCRg"
      },
      "source": [
        "이제 특정 `struct` 속성을 기준으로 배열을 필터링할 수 있을 뿐만 아니라 해당 속성을 가진 모든 구조체도 필터링할 수 있습니다!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6g3pPZIuMvPu"
      },
      "source": [
        "# 좋은 API 설계를 위한 팁\n",
        "\n",
        "***이 섹션은 [WWDC 2019: Modern Swift API Design](https://developer.apple.com/videos/play/wwdc2019/415/) 토크에서 발췌했습니다.***\n",
        "\n",
        "이제 프로토콜의 동작 방식을 이해했으므로 프로토콜을 언제 사용해야 하는지 살펴보는 것이 좋겠습니다. 프로토콜이 강력한 도구가 될 수도 있지만, 그렇다고 무작정 프로토콜을 시작하고 보는 것이 최선은 아닐 수도 있습니다.\n",
        "\n",
        "- 구체적인 사용 사례로 시작하세요.\n",
        "    - 먼저 구체적인 형식으로 사용 사례를 탐색하고 어떤 코드가 반복되고 있는 것을 공유하고 찾고자 하는지 파악하세요. 그런 다음 제네릭과 공유된 코드를 인수 분해하세요. 새로운 프로토콜을 만드는 것을 의미할 수 있습니다. 제네릭 코드가 필요한지 알아보세요.\n",
        "- 표준 라이브러리에 정의된 기존 프로토콜에서 새 프로토콜을 구성하는 것을 고려하세요. 이에 대한 좋은 예는 다음 [Apple 설명서](https://developer.apple.com/documentation/swift/adopting_common_protocols)를 참조하세요.\n",
        "- 제네릭 프로토콜 대신 제네릭 형식을 정의하는 것이 좋습니다.\n",
        "\n",
        "## 예: 사용자 정의 벡터 형식 정의하기\n",
        "\n",
        "3가지 중요한 벡터 연산을 정의하는 일부 기하학 앱에서 사용할 부동 소수점 숫자에 `GeometricVector` 프로토콜을 정의하고 싶다고 가정해 보겠습니다.\n",
        "\n",
        "```swift\n",
        "protocol GeometricVector {     associatedtype Scalar: FloatingPoint     static func dot(_ a: Self, _ b: Self) -> Scalar     var length: Scalar { get }     func distance(to other: Self) -> Scalar }\n",
        "```\n",
        "\n",
        "`SIMD` 프로토콜이 도움이 될 수 있는 벡터의 차원을 저장하고 싶다고 가정해 봅시다. 그래서 새로운 형식에서 `SIMD` 프로토콜을 구체화하도록 만들 것입니다. `SIMD` 벡터는 벡터 연산을 수행하는 데 사용하면 매우 빠른 고정 크기 벡터로 생각할 수 있습니다.\n",
        "\n",
        "```swift\n",
        "protocol GeometricVector: SIMD {     associatedtype Scalar: FloatingPoint     static func dot(_ a: Self, _ b: Self) -> Scalar     var length: Scalar { get }     func distance(to other: Self) -> Scalar }\n",
        "```\n",
        "\n",
        "이제 위 연산의 기본 구현을 정의하겠습니다.\n",
        "\n",
        "```swift\n",
        "extension GeometricVector {     static func dot(_ a: Self, _ b: Self) -> Scalar {         (a * b).sum()     }      var length: Scalar {         Self.dot(self, self).squareRoot()     }      func distance(to other: Self) -> Scalar {         (self - other).length     } }\n",
        "```\n",
        "\n",
        "그런 다음 이러한 기능을 추가하려는 각 형식에 규칙을 추가해야 합니다.\n",
        "\n",
        "```swift\n",
        "extension SIMD2: GeometricVector where Scalar: FloatingPoint { } extension SIMD3: GeometricVector where Scalar: FloatingPoint { } extension SIMD4: GeometricVector where Scalar: FloatingPoint { } extension SIMD8: GeometricVector where Scalar: FloatingPoint { } extension SIMD16: GeometricVector where Scalar: FloatingPoint { } extension SIMD32: GeometricVector where Scalar: FloatingPoint { } extension SIMD64: GeometricVector where Scalar: FloatingPoint { }\n",
        "```\n",
        "\n",
        "프로토콜을 정의하고 기본 구현을 제공한 다음 여러 형식에 대한 규칙을 추가하는 이 3단계 프로세스는 상당히 반복적입니다.\n",
        "\n",
        "## 프로토콜이 필요했을까요?\n",
        "\n",
        "`SIMD` 형식에 고유한 구현이 없다는 사실은 경고 신호입니다. 따라서 이 경우 프로토콜은 실제로 우리에게 아무것도 제공하지 않습니다.\n",
        "\n",
        "## `SIMD` 의 확장으로 정의하기\n",
        "\n",
        "`SIMD` 프로토콜의 확장에 3개의 연산자를 작성하면 문제를 보다 간결하게 해결할 수 있습니다.\n",
        "\n",
        "```swift\n",
        "extension SIMD where Scalar: FloatingPoint {     static func dot(_ a: Self, _ b: Self) -> Scalar {         (a * b).sum()     }      var length: Scalar {         Self.dot(self, self).squareRoot()     }      func distance(to other: Self) -> Scalar {         (self - other).length     } }\n",
        "```\n",
        "\n",
        "더 적은 수의 코드 줄을 사용하여 모든 형식의 `SIMD`에 모든 기본 구현을 추가했습니다.\n",
        "\n",
        "때로는 이러한 형식의 계층 구조를 만들고 싶을 수 있지만 항상 필요한 것은 아닙니다. 이것은 또한 컴파일된 프로그램의 이진 크기가 더 작아지고 코드가 더 빨리 컴파일된다는 것을 의미합니다.\n",
        "\n",
        "이 확장 방법은 추가하려는 메서드가 여러 개 있을 때 유용합니다. 그러나 더 큰 API를 설계할 때 확장성 문제가 발생하기도 합니다.\n",
        "\n",
        "## ~입니다? ~있습니다?\n",
        "\n",
        "앞서 `GeometricVector`에서 `SIMD`를 구체화할 것이라고 말했습니다. 그러나 이것은 '~입니다' 관계일까요? 문제는 `SIMD`가 벡터에 스칼라 1을 추가할 수 있는 연산을 정의하지만, 기하학 컨텍스트에서 이러한 연산을 정의하는 것은 의미가 없다는 것입니다.\n",
        "\n",
        "따라서 부동 소수점 숫자를 처리할 수 있는 새로운 제네릭 형식으로 `SIMD`를 래핑하면 '~있습니다' 관계가 더 나을 수 있습니다.\n",
        "\n",
        "```swift\n",
        "// NOTE: `Storage` is the underlying type that is storing the values,  // just like in a `SIMD` vector. struct GeometricVector<Storage: SIMD> where Storage.Scalar: FloatingPoint {     typealias Scalar = Storage.Scalar     var value: Storage     init(_ value: Storage) { self.value = value } }\n",
        "```\n",
        "\n",
        "그런 다음 조심해서 기하학 컨텍스트에서만 의미가 있는 연산만 정의합니다.\n",
        "\n",
        "```swift\n",
        "extension GeometricVector {     static func + (a: Self, b: Self) -> Self {         Self(a.value + b.value)     }      static func - (a: Self, b: Self) -> Self {         Self(a.value - b.value)     }     static func * (a: Self, b: Scalar) -> Self {         Self(a.value * b)     } }\n",
        "```\n",
        "\n",
        "또한 제네릭 확장을 사용하여 이전과 거의 똑같은 구현을 원하는 이전 연산자 3개를 얻을 수 있습니다.\n",
        "\n",
        "```swift\n",
        "extension GeometricVector {     static func dot(_ a: Self, _ b: Self) -> Scalar {         (a.value * b.value).sum()     }      var length: Scalar {         Self.dot(self, self).squareRoot()     }      func distance(to other: Self) -> Scalar {         (self - other).length     } }\n",
        "```\n",
        "\n",
        "전반적으로 단순히 구조체를 사용하여 3가지 연산의 동작을 형식으로 구체화할 수 있었습니다. 프로토콜을 사용했을 때 모든 `SIMD` 벡터에 대해 반복적인 규칙을 작성하는 문제에 직면했으며 `Scalar + Vector`와 같은 특정 연산자의 사용을 방지할 수 없었습니다(이 경우에는 원하지 않음). 따라서 프로토콜이 만병통치약과 같은 솔루션이 아니라는 점을 기억하세요. 때로는 더 전통적인 솔루션이 더 강력할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UVLumcugImNx"
      },
      "source": [
        "# 더 많은 프로토콜 지향 프로그래밍 리소스\n",
        "\n",
        "논의된 주제에 대한 추가 리소스는 다음과 같습니다.\n",
        "\n",
        "- [WWDC 2015: Swift의 프로토콜 지향 프로그래밍](https://developer.apple.com/videos/play/wwdc2015/408/): Swift 2를 사용하여 발표되었으므로 그 이후로 많은 부분이 변경되었지만 (예: 프레젠테이션에서 사용한 프로토콜의 이름) 여전히 이론 및 이론에 기반한 사용 방법에 대한 좋은 리소스입니다.\n",
        "- [Swift 3의 프로토콜 지향 프로그래밍 소개](https://www.raywenderlich.com/814-introducing-protocol-oriented-programming-in-swift-3): 이것은 Swift 3로 작성되었으므로 성공적으로 컴파일하려면 일부 코드를 수정해야 할 수 있지만 또 다른 훌륭한 리소스입니다.\n",
        "- [WWDC 2019: Modern Swift API Design](https://developer.apple.com/videos/play/wwdc2019/415/): API 설계에서 프로토콜이 더 안 좋은 선택이 될 수 있는 사용 사례(상기 '좋은 API를 위한 팁' 섹션과 같음), 값과 참조 형식 간의 차이, 키 경로 멤버 조회 및 속성 래퍼를 살펴봅니다.\n",
        "- [Generics](https://docs.swift.org/swift-book/LanguageGuide/Generics.html): 제네릭에 관한 Swift 5의 Swift 자체 설명서"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "protocol_oriented_generics.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Swift",
      "name": "swift"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
